"""
认证服务模块

实现用户认证的业务逻辑，包括用户注册、登录、密码管理等功能。
"""

from datetime import datetime, timedelta
from typing import Optional, Tuple, Dict, Any
import secrets
import logging

from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, update
from sqlalchemy.exc import IntegrityError
from jose import jwt, JWTError

from app.core.security import verify_password, get_password_hash, ALGORITHM
from app.core.config import settings as app_settings
from app.models.user import User
from app.modules.auth.config import settings

# 配置日志
logger = logging.getLogger(__name__)


class AuthService:
    """
    认证服务类
    
    提供用户认证和授权相关的所有功能。
    """
    
    async def register_user(
        self, db: AsyncSession, *, email: str, password: str, full_name: Optional[str] = None
    ) -> Tuple[Optional[User], Optional[str]]:
        """
        注册新用户
        
        Args:
            db: 数据库会话
            email: 电子邮件
            password: 密码
            full_name: 全名(可选)
            
        Returns:
            元组 (用户对象, 错误信息)，如果注册成功则错误信息为None
        """
        # 检查用户是否已存在
        stmt = select(User).where(User.email == email)
        result = await db.execute(stmt)
        existing_user = result.scalar_one_or_none()
        
        if existing_user:
            return None, "该邮箱已被注册"
        
        # 创建新用户
        hashed_password = get_password_hash(password)
        user = User(
            email=email,
            hashed_password=hashed_password,
            full_name=full_name,
            is_email_verified=not settings.EMAIL_VERIFICATION_REQUIRED
        )
        
        try:
            db.add(user)
            await db.commit()
            await db.refresh(user)
            
            # 如果需要邮箱验证，在这里处理
            if settings.EMAIL_VERIFICATION_REQUIRED:
                # 这里可以添加发送验证邮件的代码
                pass
                
            return user, None
        except IntegrityError:
            await db.rollback()
            return None, "注册失败，请稍后再试"
        except Exception as e:
            logger.error(f"用户注册异常: {str(e)}")
            await db.rollback()
            return None, "注册过程中发生错误"
    
    async def authenticate_user(
        self, db: AsyncSession, *, email: str, password: str
    ) -> Optional[User]:
        """
        验证用户身份
        
        Args:
            db: 数据库会话
            email: 电子邮件
            password: 密码
            
        Returns:
            如果验证成功，返回用户对象；否则返回None
        """
        # 获取用户
        stmt = select(User).where(User.email == email)
        result = await db.execute(stmt)
        user = result.scalar_one_or_none()
        
        # 验证用户存在且密码正确
        if not user or not verify_password(password, user.hashed_password):
            return None
        
        # 验证用户是否激活
        if not user.is_active:
            return None
        
        # 如果需要邮箱验证，检查邮箱是否已验证
        if settings.EMAIL_VERIFICATION_REQUIRED and not user.is_email_verified:
            return None
            
        return user
    
    def create_access_token(self, user_id: int) -> Tuple[str, datetime]:
        """
        创建访问令牌
        
        Args:
            user_id: 用户ID
            
        Returns:
            元组 (访问令牌, 过期时间)
        """
        # 计算过期时间
        expires_delta = timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)
        expire = datetime.utcnow() + expires_delta
        
        # 创建JWT载荷
        to_encode = {"sub": str(user_id), "exp": expire}
        
        # 签名并返回令牌
        token = jwt.encode(to_encode, app_settings.SECRET_KEY, algorithm=ALGORITHM)
        return token, expire
    
    async def change_password(
        self, db: AsyncSession, *, user_id: int, current_password: str, new_password: str
    ) -> Tuple[bool, Optional[str]]:
        """
        修改用户密码
        
        Args:
            db: 数据库会话
            user_id: 用户ID
            current_password: 当前密码
            new_password: 新密码
            
        Returns:
            元组 (是否成功, 错误信息)，如果成功则错误信息为None
        """
        # 获取用户
        stmt = select(User).where(User.id == user_id)
        result = await db.execute(stmt)
        user = result.scalar_one_or_none()
        
        if not user:
            return False, "用户不存在"
        
        # 验证当前密码
        if not verify_password(current_password, user.hashed_password):
            return False, "当前密码不正确"
        
        # 如果新密码与当前密码相同，则无需更新
        if verify_password(new_password, user.hashed_password):
            return False, "新密码不能与当前密码相同"
        
        # 更新密码
        hashed_password = get_password_hash(new_password)
        stmt = (
            update(User)
            .where(User.id == user_id)
            .values(hashed_password=hashed_password)
        )
        
        try:
            await db.execute(stmt)
            await db.commit()
            return True, None
        except Exception as e:
            logger.error(f"修改密码异常: {str(e)}")
            await db.rollback()
            return False, "修改密码失败，请稍后再试"
    
    async def get_user_by_email(
        self, db: AsyncSession, *, email: str
    ) -> Optional[User]:
        """
        通过邮箱获取用户
        
        Args:
            db: 数据库会话
            email: 电子邮件
            
        Returns:
            用户对象，如果不存在则返回None
        """
        stmt = select(User).where(User.email == email)
        result = await db.execute(stmt)
        return result.scalar_one_or_none()
    
    async def get_user_by_id(
        self, db: AsyncSession, *, user_id: int
    ) -> Optional[User]:
        """
        通过ID获取用户
        
        Args:
            db: 数据库会话
            user_id: 用户ID
            
        Returns:
            用户对象，如果不存在则返回None
        """
        stmt = select(User).where(User.id == user_id)
        result = await db.execute(stmt)
        return result.scalar_one_or_none()
    
    def generate_password_reset_token(self, email: str) -> str:
        """
        生成密码重置令牌
        
        Args:
            email: 用户电子邮件
            
        Returns:
            密码重置令牌
        """
        # 计算过期时间
        expires_delta = timedelta(hours=settings.RESET_TOKEN_EXPIRE_HOURS)
        expire = datetime.utcnow() + expires_delta
        
        # 创建JWT载荷
        to_encode = {"sub": email, "exp": expire, "type": "password_reset"}
        
        # 签名并返回令牌
        token = jwt.encode(to_encode, app_settings.SECRET_KEY, algorithm=ALGORITHM)
        return token
    
    def verify_password_reset_token(self, token: str) -> Optional[str]:
        """
        验证密码重置令牌
        
        Args:
            token: 密码重置令牌
            
        Returns:
            如果令牌有效，返回用户电子邮件；否则返回None
        """
        try:
            payload = jwt.decode(token, app_settings.SECRET_KEY, algorithms=[ALGORITHM])
            
            # 验证令牌类型和有效期
            if payload.get("type") != "password_reset":
                return None
                
            email: str = payload.get("sub")
            if email is None:
                return None
                
            return email
        except JWTError:
            return None
            
    async def reset_password(
        self, db: AsyncSession, *, token: str, new_password: str
    ) -> Tuple[bool, Optional[str]]:
        """
        重置用户密码
        
        Args:
            db: 数据库会话
            token: 密码重置令牌
            new_password: 新密码
            
        Returns:
            元组 (是否成功, 错误信息)，如果成功则错误信息为None
        """
        # 验证令牌
        email = self.verify_password_reset_token(token)
        if not email:
            return False, "无效或已过期的重置链接"
        
        # 获取用户
        user = await self.get_user_by_email(db, email=email)
        if not user:
            return False, "用户不存在"
        
        # 更新密码
        hashed_password = get_password_hash(new_password)
        stmt = (
            update(User)
            .where(User.email == email)
            .values(hashed_password=hashed_password)
        )
        
        try:
            await db.execute(stmt)
            await db.commit()
            return True, None
        except Exception as e:
            logger.error(f"重置密码异常: {str(e)}")
            await db.rollback()
            return False, "重置密码失败，请稍后再试"
    
    def generate_email_verification_token(self, email: str) -> str:
        """
        生成邮箱验证令牌
        
        Args:
            email: 用户电子邮件
            
        Returns:
            邮箱验证令牌
        """
        # 计算过期时间
        expires_delta = timedelta(hours=settings.EMAIL_VERIFY_TOKEN_EXPIRE_HOURS)
        expire = datetime.utcnow() + expires_delta
        
        # 创建JWT载荷
        to_encode = {"sub": email, "exp": expire, "type": "email_verification"}
        
        # 签名并返回令牌
        token = jwt.encode(to_encode, app_settings.SECRET_KEY, algorithm=ALGORITHM)
        return token
    
    def verify_email_token(self, token: str) -> Optional[str]:
        """
        验证邮箱验证令牌
        
        Args:
            token: 邮箱验证令牌
            
        Returns:
            如果令牌有效，返回用户电子邮件；否则返回None
        """
        try:
            payload = jwt.decode(token, app_settings.SECRET_KEY, algorithms=[ALGORITHM])
            
            # 验证令牌类型和有效期
            if payload.get("type") != "email_verification":
                return None
                
            email: str = payload.get("sub")
            if email is None:
                return None
                
            return email
        except JWTError:
            return None
    
    async def verify_email(
        self, db: AsyncSession, *, token: str
    ) -> Tuple[bool, Optional[str]]:
        """
        验证用户邮箱
        
        Args:
            db: 数据库会话
            token: 邮箱验证令牌
            
        Returns:
            元组 (是否成功, 错误信息)，如果成功则错误信息为None
        """
        # 验证令牌
        email = self.verify_email_token(token)
        if not email:
            return False, "无效或已过期的验证链接"
        
        # 获取用户
        user = await self.get_user_by_email(db, email=email)
        if not user:
            return False, "用户不存在"
        
        # 如果邮箱已验证，则无需更新
        if user.is_email_verified:
            return True, None
        
        # 更新邮箱验证状态
        stmt = (
            update(User)
            .where(User.email == email)
            .values(is_email_verified=True)
        )
        
        try:
            await db.execute(stmt)
            await db.commit()
            return True, None
        except Exception as e:
            logger.error(f"邮箱验证异常: {str(e)}")
            await db.rollback()
            return False, "邮箱验证失败，请稍后再试"
    
    async def update_user_profile(
        self, db: AsyncSession, *, user_id: int, full_name: Optional[str] = None
    ) -> Tuple[Optional[User], Optional[str]]:
        """
        更新用户资料
        
        Args:
            db: 数据库会话
            user_id: 用户ID
            full_name: 新的全名(可选)
            
        Returns:
            元组 (更新后的用户对象, 错误信息)，如果成功则错误信息为None
        """
        # 获取用户
        user = await self.get_user_by_id(db, user_id=user_id)
        if not user:
            return None, "用户不存在"
        
        # 构建更新数据
        update_data = {}
        if full_name is not None:
            update_data["full_name"] = full_name
        
        # 如果没有要更新的数据，直接返回用户
        if not update_data:
            return user, None
        
        # 更新用户资料
        stmt = (
            update(User)
            .where(User.id == user_id)
            .values(**update_data)
        )
        
        try:
            await db.execute(stmt)
            await db.commit()
            
            # 重新获取更新后的用户
            user = await self.get_user_by_id(db, user_id=user_id)
            return user, None
        except Exception as e:
            logger.error(f"更新用户资料异常: {str(e)}")
            await db.rollback()
            return None, "更新资料失败，请稍后再试" 